package top.z.work.module.system.service.impl;

import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.captcha.CaptchaUtil;
import cn.hutool.captcha.LineCaptcha;
import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.core.util.UpdateEntity;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import top.z.work.framework.common.constant.DataStatus;
import top.z.work.framework.common.exception.ExceptionUtil;
import top.z.work.framework.common.pojo.R;
import top.z.work.framework.common.util.IpUtil;
import top.z.work.framework.common.util.ServletUtil;
import top.z.work.module.system.cache.CaptchaCache;
import top.z.work.module.system.constant.LoginInfoStatus;
import top.z.work.module.system.constant.SystemErrorCode;
import top.z.work.module.system.controller.authorize.vo.AuthorizeCaptchaVO;
import top.z.work.module.system.controller.authorize.vo.AuthorizeInfoVO;
import top.z.work.module.system.controller.authorize.vo.AuthorizeLoginVO;
import top.z.work.module.system.controller.authorize.vo.AuthorizeUserVO;
import top.z.work.module.system.controller.route.vo.SystemRouteVO;
import top.z.work.module.system.convert.AuthorizeConvert;
import top.z.work.module.system.convert.SystemRouteConvert;
import top.z.work.module.system.entity.SystemLoginInfo;
import top.z.work.module.system.entity.SystemRoute;
import top.z.work.module.system.entity.SystemUser;
import top.z.work.module.system.entity.SystemUserOnline;
import top.z.work.module.system.service.*;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;

/**
 * 授权相关逻辑
 *
 * @author zaoangod
 * @since 2024-03-22
 */
@Slf4j
@Service
public class AuthorizeServiceImpl implements AuthorizeService {

    // 验证码的开关
    @Value("${top-work.captcha.enable:true}")
    private Boolean CAPTCHA_ENABLE;
    // 登录超时
    @Value("${top-work.token.timeout:3600}")
    private Integer TIMEOUT;

    @Resource
    private CaptchaCache            captchaCache;
    @Resource
    private PermissionService       permissionService;
    @Resource
    private SystemUserService       systemUserService;
    @Resource
    private SystemLoginInfoService  systemLoginInfoService;
    @Resource
    private SystemUserOnlineService systemUserOnlineService;

    /**
     * 生成验证码
     */
    @Override
    public AuthorizeCaptchaVO generate() {
        // 生成验证码key
        String key = uuid();
        // 定义图形验证码的长、宽、验证码字符数、干扰元素个数
        LineCaptcha captcha = CaptchaUtil.createLineCaptcha(200, 100, 4, 4);
        String      img     = captcha.getImageBase64Data();
        captchaCache.set(key, captcha.getCode());
        log.info("Key: {}, code: {}", key, captcha.getCode());
        return new AuthorizeCaptchaVO().setKey(key).setImg(img);
    }

    /**
     * 验证码效验
     *
     * @param key  key
     * @param code 验证码
     * @return true：成功  false：失败
     */
    @Override
    public Boolean validate(String key, String code) {
        String val = captchaCache.get(key);
        captchaCache.delete(key);
        return val != null && val.equalsIgnoreCase(code);
    }

    /**
     * 账号密码登录
     *
     * @param login 登录信息
     */
    @Override
    public String login(AuthorizeLoginVO login) {
        // 验证码校验
        log.info("验证码校验");
        if (CAPTCHA_ENABLE) {
            boolean validate = this.validate(login.getKey(), login.getCaptcha());
            log.info("验证码校验: Key: {}, Captcha: {}", login.getKey(), login.getCaptcha());
            if (!validate) {
                // 验证码不正确
                createLoginLog(null, login.getUsername(), LoginInfoStatus.Failure, SystemErrorCode.AUTH_CAPTCHA_ERROR.message());
                throw ExceptionUtil.exception(SystemErrorCode.AUTH_CAPTCHA_ERROR);
            }
        }
        // 校验账号是否存在
        log.info("校验账号是否存在");

        QueryWrapper query = new QueryWrapper().eq(SystemUser::getUsername, login.getUsername());
        SystemUser   user  = systemUserService.getOne(query);
        if (user == null) {
            createLoginLog(null, login.getUsername(), LoginInfoStatus.Failure, SystemErrorCode.AUTH_ACCOUNT_NOT_EXIST.message());
            throw ExceptionUtil.exception(SystemErrorCode.AUTH_ACCOUNT_NOT_EXIST);
        }
        // 验证密码
        log.info("验证密码");
        String encryptPassword = systemUserService.encryptPassword(login.getUsername(), login.getPassword(), user.getSalt());
        log.debug("{}", encryptPassword);
        if (!user.getPassword().equals(encryptPassword)) {
            createLoginLog(null, login.getUsername(), LoginInfoStatus.Failure, SystemErrorCode.AUTH_PASSWORD_ERROR.message());
            throw ExceptionUtil.exception(SystemErrorCode.AUTH_PASSWORD_ERROR);
        }
        // 校验是否禁用
        log.info("校验是否禁用");
        if (DataStatus.NEGATIVE.code().equals(user.getStatus())) {
            createLoginLog(null, login.getUsername(), LoginInfoStatus.Failure, SystemErrorCode.AUTH_ACCOUNT_DISABLE.message());
            throw ExceptionUtil.exception(SystemErrorCode.AUTH_ACCOUNT_DISABLE);
        }
        // 创建 Token 令牌, 记录登录日志
        log.info("创建 Token 令牌, 记录登录日志");
        return createTokenAfterLoginSuccess(user);
    }

    /**
     * 注销
     */
    @Override
    public void logout() {
        String       token = StpUtil.getTokenValue();
        QueryWrapper query = new QueryWrapper().eq(SystemUserOnline::getToken, token);
        systemUserOnlineService.remove(query);

        StpUtil.logout();
    }

    /**
     * 获取登录后的授权信息
     */
    @Override
    public R<AuthorizeInfoVO> accountInfo() {
        log.info("LoginId: {}", StpUtil.getLoginIdDefaultNull());
        // 获取登录账号信息
        AuthorizeUserVO userInfo = Optional.ofNullable(StpUtil.getLoginIdDefaultNull())
                .map(id -> Long.parseLong(id.toString()))
                .map(id -> QueryWrapper.create().eq(SystemUser::getIdentity, id))
                .map(query -> systemUserService.getOne(query))
                .map(AuthorizeConvert.INSTANCE::convert)
                .orElseThrow(() -> new NotLoginException(NotLoginException.NOT_TOKEN_MESSAGE, "", NotLoginException.NOT_TOKEN));

        // 账号许可列表
        List<String> permissionList = permissionService.getUserPermissionListFromCache(userInfo.getIdentity());

        // 账号角色列表
        List<String> roleCodeList = permissionService.getUserRoleCodeListFromCache(userInfo.getIdentity());

        // 账号路由列表
        List<SystemRoute>   routeList   = permissionService.findUserRoute(userInfo.getIdentity());
        List<SystemRouteVO> routeVOList = SystemRouteConvert.INSTANCE.convert(routeList);

        AuthorizeInfoVO authorizeInfo = new AuthorizeInfoVO()
                .setUserInfo(userInfo)
                .setRouteList(routeVOList)
                .setRoleList(roleCodeList)
                .setPermissionList(permissionList);

        return R.s(authorizeInfo);
    }

    /**
     * 登陆成功之后创建Token
     *
     * @param user 用户
     * @return Token
     */
    private String createTokenAfterLoginSuccess(SystemUser user) {
        String token = this.uuid();
        // 登录成功
        SaLoginModel model = SaLoginModel.create()
                .setDevice("")             // 此次登录的客户端设备类型, 用于[同端互斥登录]时指定此次登录的设备类型
                .setToken(token)           // 预定此次登录生成的Token
                .setTimeout(TIMEOUT)       // 指定此次登录token的有效期, 单位:秒(如未指定, 自动取全局配置的 timeout 值)
                .setIsWriteHeader(false)   // 是否在登录后将 Token 写入到响应头
                .setIsLastingCookie(false);// 是否为持久Cookie(临时Cookie在浏览器关闭时会自动删除, 持久Cookie在重新打开后依然存在)
        StpUtil.login(user.getIdentity(), model);
        // 插入登陆成功日志
        this.createLoginLog(user.getIdentity(), user.getUsername(), LoginInfoStatus.Success, "登录成功");
        // 创建会话日志
        this.createUserOnline(user.getUsername(), token, TIMEOUT);
        // 返回token
        return token;
    }

    private void createUserOnline(String username, String token, Integer timeout) {
        SystemUserOnline systemUserOnline = new SystemUserOnline();
        systemUserOnline.setLoginName(username);
        systemUserOnline.setToken(token);
        systemUserOnline.setLoginTime(LocalDateTime.now());
        systemUserOnline.setExpireTime(LocalDateTime.now().plusSeconds(timeout));
        systemUserOnlineService.save(systemUserOnline);
    }

    /**
     * 创建登录日志
     *
     * @param username  用户名称
     * @param operation 结果
     */
    private void createLoginLog(Long userId, String username, LoginInfoStatus loginInfoStatus, String operation) {
        // 插入登录日志
        SystemLoginInfo loginInfo = new SystemLoginInfo();
        loginInfo.setUsername(username);
        loginInfo.setIpAddress(IpUtil.getIp());
        loginInfo.setUserAgent(ServletUtil.getUserAgent());
        loginInfo.setStatus(loginInfoStatus.getStatus());
        loginInfo.setOperation(operation);
        loginInfo.setLoginTime(LocalDateTime.now());
        // 更新最后登录时间
        if (userId != null && Objects.equals(loginInfoStatus.getStatus(), LoginInfoStatus.Success.getStatus())) {
            SystemUser user = UpdateEntity.of(SystemUser.class, userId);
            user.setLoginTime(LocalDateTime.now());
            systemUserService.updateById(user);
        }
        systemLoginInfoService.save(loginInfo);
    }

    private String uuid() {
        return UUID.randomUUID().toString().replace("-", "");
    }
}
